#ifndef PERFORMANCE_H_
#define PERFORMANCE_H_

#include <opencv2/opencv.hpp>
#include <vector>
#include <Windows.h>

namespace perf {

  using namespace cv;
  using namespace cv::dnn;
  using namespace std;

  class ConvParam {
  public:
    int kernel_h = 3;
    int kernel_w = 3;
    int channel = 10;
    bool dw = false;
    int height = 200;
    int width = 200;
    int stride = 1;
    int group() const {
      return dw ? channel : 1;
    }
    vector<int> weight_size() const {
      return { channel, channel / group(), kernel_h, kernel_w };
    }
    vector<int> input_size() const {
      return { 1, channel, height, width };
    }
    String info() const {
      String info = format("[%sconv] (%d,%d) %d (%d,%d) %d", dw ? "dw" : "",
        height, width, channel, kernel_h, kernel_w, stride);
      return info;
    }
  };

  double pref_conv(ConvParam cparam, int ntimes = 100) {
    LayerParams param;
    param.type = "Convolution";
    param.name = "testConv";
    param.set("kernel_h", cparam.kernel_h);
    param.set("kernel_w", cparam.kernel_w);
    param.set("stride", cparam.stride);
    param.set("num_output", cparam.channel);
    param.set("group", cparam.group());
    Mat weights(cparam.weight_size(), CV_32F);
    randu(weights, -1.0f, 1.0f);
    Mat bias(1, cparam.channel, CV_32F);
    randu(bias, -1.0f, 1.0f);
    param.blobs.push_back(weights);
    param.blobs.push_back(bias);

    Net net;
    net.addLayerToPrev(param.name, param.type, param);
    Mat input(cparam.input_size(), CV_32F);
    randu(input, -1.0f, 1.0f);
    net.setInput(input);
    net.setPreferableBackend(DNN_BACKEND_OPENCV);
    net.setPreferableTarget(DNN_TARGET_CPU);
    // warmup
    Mat output = net.forward();
    TickMeter tm;
    tm.reset(), tm.start();
    for (int i = 0; i < ntimes; i++)
      net.forward();
    tm.stop();
    double time = tm.getTimeMilli() / ntimes;
    return time;
  }

  void run_pref_conv(int sleep=1000)
  {
    ConvParam cparam;
    for (int i = 8; i <= 456; i+=8) {
      cparam.channel = i;
      cparam.dw = false;
      cout << cparam.info();
      double time1 = pref_conv(cparam);
      Sleep(sleep);
      cparam.dw = true;
      double time2 = pref_conv(cparam);
      cout << format("\t %.3f %.3f\n", time1, time2);
      Sleep(sleep);
    }
  }

}

#endif // PERFORMANCE_H_